package org.anyrunner;

import com.intellij.execution.JavaExecutionUtil;
import com.intellij.execution.Location;
import com.intellij.execution.RunConfigurationExtension;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.execution.junit.JUnitConfigurationProducer;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.AnnotationTargetsSearch;
import org.jetbrains.annotations.NotNull;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;

public class ModifiedJUnitConfigurationProducer extends JUnitConfigurationProducer {

    private PsiFile containingFile;

    @Override
    public PsiElement getSourceElement() {
        return containingFile;
    }

    @Override
    protected RunnerAndConfigurationSettings createConfigurationByElement(Location location, ConfigurationContext context) {
        PsiClass testCase = testCaseForThisFileType(location);
        return (testCase == null) ? null : createConfiguration(context, project(location), testCase);
    }

    @Override
    protected RunnerAndConfigurationSettings findExistingByElement(@NotNull Location location,
                                                                   @NotNull RunnerAndConfigurationSettings[] existingConfigurations,
                                                                   ConfigurationContext context) {
        if (testCaseForThisFileType(location) == null) return null;
        for (RunnerAndConfigurationSettings existingConfiguration : existingConfigurations) {
            final JUnitConfiguration unitConfiguration = (JUnitConfiguration) existingConfiguration.getConfiguration();
            String name = unitConfiguration.getName();
            // TODO - CAS - 10/08/2011 - Base this on a new subtype of the RunConfigurationType
            if (name.equals("Anyrunner: " + fileName())) {
                return existingConfiguration;
            }
        }
        return null;
    }

    private Project project(Location location) {
        return location.getProject();
    }

    private RunnerAndConfigurationSettings createConfiguration(ConfigurationContext context, Project project, PsiClass testCaseForThisFileType) {
        RunnerAndConfigurationSettings settings = cloneTemplateConfiguration(project, context);
        final JUnitConfiguration configuration = (JUnitConfiguration) settings.getConfiguration();
        setupConfigurationModule(context, configuration);
        final Module originalModule = configuration.getConfigurationModule().getModule();
        configuration.restoreOriginalModule(originalModule);
        configuration.beClassConfiguration(testCaseForThisFileType);
        configuration.setVMParameters(escapeFilename(filePath()));
        configuration.setName("Anyrunner: " + fileName());
        RunConfigurationExtension.patchCreatedConfiguration(configuration);
        return settings;
    }

    private PsiClass testCaseForThisFileType(Location location) {
        containingFile = location.getPsiElement().getContainingFile();
        if (containingFile == null) return null;

        Collection<PsiModifierListOwner> anyrunnerAnnotatedClasses = anyrunnerAnnotatedClasses(project(location));
        if(anyrunnerAnnotatedClasses == null) return null;

        for (PsiModifierListOwner anyrunnerAnnotatedClass : anyrunnerAnnotatedClasses) {
            if (supports((PsiClass) anyrunnerAnnotatedClass, fileExtension()))  return (PsiClass) anyrunnerAnnotatedClass;
        }

        return null;
    }

    private Collection<PsiModifierListOwner> anyrunnerAnnotatedClasses(Project project) {
        return AnnotationTargetsSearch.search(anyrunnerMarker(project), everythingIn(project)).findAll();
    }

    private String fileName() {
        return containingFile.getVirtualFile().getName();
    }

    private String filePath() {
        return containingFile.getVirtualFile().getPath();
    }

    private String fileExtension() {
        return containingFile.getVirtualFile().getExtension();
    }

    private boolean supports(PsiClass someClass, String fileExtension) {
        // TODO - CAS - 10/08/2011 - Manage file types based on a name/value in the annotation
        return fileExtension.toLowerCase().startsWith("htm");
    }

    private PsiClass anyrunnerMarker(Project project) {
        return JavaExecutionUtil.findMainClass(project, AnyrunnerMarker.class.getCanonicalName(), everythingIn(project));
    }

    private GlobalSearchScope everythingIn(Project project) {
        return GlobalSearchScope.allScope(project);
    }

    private String escapeFilename(String filePath) {
        return "-Dsource.file=\"" + encode(filePath) + "\"";
    }

    private String encode(String params) {
        try {
            return URLEncoder.encode(params, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("We think UTF-8 should probably be supported");
        }
    }
}
